﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace PhotoManager
{
    public class PhotoInfoCache
    {
        private readonly Dictionary<string, PhotoInfo> m_cache =
            new Dictionary<string, PhotoInfo>(); // FileInfoKey => PhotoInfo

        private readonly Stream m_stream;
        private readonly StreamReader m_reader;
        private readonly StreamWriter m_writer;
        private bool m_isDisposed;

        public PhotoInfoCache(string fileName)
        {
            if (fileName == null)
                throw new ArgumentNullException("fileName");

            m_stream = new FileStream(
                fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);

            // Create a stream reader and read existing records. Keep the
            // stream reader open to avoid closing the underlying stream.
            m_reader = new StreamReader(m_stream, Encoding.UTF8);
            for (string line; (line = m_reader.ReadLine()) != null; )
            {
                try
                {
                    var entry = CacheEntry.Parse(line);
                    m_cache[entry.Key] = entry.Value;
                }
                catch (Exception) { }
            }

            // Create the stream writer to cache new records later.
            m_writer = new StreamWriter(m_stream, Encoding.UTF8);
            m_writer.AutoFlush = true;
        }

        /// <summary>
        /// Finds photo info for the given file.
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <returns>The photo info if found, or null otherwise.</returns>
        public PhotoInfo FindPhotoInfo(FileInfo fileInfo)
        {
            if (fileInfo == null)
                throw new ArgumentNullException("fileInfo");

            PhotoInfo photoInfo;
            m_cache.TryGetValue(ComputeFileInfoKey(fileInfo), out photoInfo);
            return photoInfo;
        }

        public void AddPhotoInfo(FileInfo fileInfo, PhotoInfo photoInfo)
        {
            if (fileInfo == null)
                throw new ArgumentNullException("fileInfo");
            if (photoInfo == null)
                throw new ArgumentNullException("photoInfo");
            if (m_isDisposed)
                throw new ObjectDisposedException("this");

            string key = ComputeFileInfoKey(fileInfo);
            m_cache[key] = photoInfo;

            var entry = new CacheEntry(key, photoInfo);
            m_writer.WriteLine(entry.ToString());
        }

        private static string ComputeFileInfoKey(FileInfo fileInfo)
        {
            if (fileInfo == null)
                throw new ArgumentException("fileInfo");

            string key = string.Format(
                    "{0}?{1}?{2:yyyyMMdd_HHmmss}",
                    fileInfo.FullName.ToLowerInvariant(), fileInfo.Length, fileInfo.LastWriteTime);

            using (SHA256 hasher = new SHA256Managed())
            {
                byte[] hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(key));
                return BitConverter.ToString(hash).Replace("-", "");
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected void Dispose(bool disposing)
        {
            if (disposing && !m_isDisposed)
            {
                m_writer.Dispose();
                m_reader.Dispose();
                m_stream.Dispose();
                m_isDisposed = true;
            }
        }

        private class CacheEntry
        {
            private readonly string m_key;
            private readonly PhotoInfo m_value;

            public string Key
            {
                get { return m_key; }
            }

            public PhotoInfo Value
            {
                get { return m_value; }
            }

            public CacheEntry(string key, PhotoInfo value)
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                m_key = key;
                m_value = value;
            }

            public override string ToString()
            {
                return string.Format("{0}={1}", Key, Value);
            }

            public static CacheEntry Parse(string s)
            {
                if (s == null)
                    throw new ArgumentNullException("s");

                string[] fields = s.Split(new char[] { '=' }, 2, StringSplitOptions.None);
                if (fields.Length != 2)
                    throw new ArgumentException("Input string must be of the form key=value.");

                return new CacheEntry(fields[0], PhotoInfo.Parse(fields[1]));
            }
        }
    }
}
